package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.entity.Setmeal;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealDishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.result.PageResult;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
public class DishServiceImpl implements DishService {

    @Autowired
    DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;



    /**
     * 新增菜品和对应的口味（菜品表和口味表）
     *
     * 后台系统中可以管理菜品信息，通过新增功能来添加一个新的菜品，在添加菜品时需要选择当前菜品所属的菜品分类，并且需要上传菜品图片。
     * 当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。
     *
     * - 菜品名称必须是唯一的
     * - 菜品必须属于某个分类下，不能单独存在
     * - 新增菜品时可以根据情况选择菜品的口味
     * - 每个菜品必须对应一张图片
     *
     *      接口设计：
     * - 根据类型查询分类（已完成）
     * - 文件上传
     * - 新增菜品
     *
     * 新增菜品，其实就是将新增页面录入的菜品信息插入到dish表，如果添加了口味做法，还需要向dish_flavor表插入数据。
     * @param dishDTO
     */
    //涉及到多个数据表的操作，要保证数据的一致性，所以要加一个事务注解，保证方法是原子性的，要么全成功要么全失败
    @Transactional   //忘了什么用----->事务
    public void saveWithFlavor(DishDTO dishDTO){

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);

        //1.向菜品表插入一条数据
        dishMapper.insert(dish);

        //获取insert语句生成的主键值(xml文件里设置了useGeneratedKeys="true")
        Long dishId = dish.getId();

        //dishDto里面口味用的是集合，先把集合数据取出来
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if ((flavors != null && flavors.size()>0)){     //表示口味数据确实提交过来了
            //把获得dishId遍历进去口味表
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });

            //2.向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }
    }

    /**
     * 菜品分页查询
     *
     * 系统中的菜品数据很多的时候，如果在一个页面中全部展示出来会显得比较乱，不便于查看，所以一般的系统中都会以分页的方式来展示列表数据。
     * 在菜品列表展示时，除了菜品的基本信息(名称、售价、售卖状态、最后操作时间)外:
     * 还有两个字段略微特殊，第一个是图片字段 ，我们从数据库查询出来的仅仅是图片的名字，图片要想在表格中回显展示出来，就需要下载这个图片。
     * 第二个是菜品分类，这里展示的是分类名称，而不是分类ID，此时我们就需要根据菜品的分类ID，去分类表中查询分类信息，然后在页面展示。
     *
     * - 根据页码展示菜品信息
     * - 每页展示10条数据
     * - 分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO){
        PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
        //返回值除了dishDto的属性，还多了个分类名称categoryName，所以另外建了个dishDo，把这个vo转成json数据给前端显示出来
        Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);

        return new PageResult(page.getTotal(),page.getResult());
    }


    @Autowired
    private SetmealDishMapper setmealDishMapper;
    /**
     * 菜品批量删除
     *
     * 在菜品列表页面，每个菜品后面对应的操作分别为修改、删除、停售，可通过删除功能完成对菜品及相关的数据进行删除。
     *
     * - 可以一次删除一个菜品，也可以批量删除菜品
     * - 起售中的菜品不能删除
     * - 被套餐关联的菜品不能删除
     * - 删除菜品后，关联的口味数据也需要删除掉
     *
     * 注意：删除一个菜品和批量删除菜品共用一个接口，故ids可包含多个菜品id,之间用逗号分隔。
     *
     * 注意事项：
     *
     * - 在dish表中删除菜品基本数据时，同时，也要把关联在dish_flavor表中的数据一块删除。
     * - setmeal_dish表为菜品和套餐关联的中间表。
     * - 若删除的菜品数据关联着某个套餐，此时，删除失败。
     * - 若要删除套餐关联的菜品数据，先解除两者关联，再对菜品进行删除。
     *
     * @param ids
     */
    @Transactional  //事务
    public void deleteBatch(List<Long> ids){
        //1.判断当前菜品是否能够删除（是否存在起售中的菜品？）
        for(Long id : ids){      //把ids的每个id遍历出来，然后根据这个id来查询菜品数据
            Dish dish = dishMapper.getById(id);

            if(dish.getStatus() == StatusConstant.ENABLE){
                //当前菜品处于起售中，不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }

        //2.判断当前菜品是否能够删除（是否被套餐关联了？）
        //如果在setmeal_dish表中，能够根据dish_id来查询到setmeal_id，就说明则菜品被套餐关联了
        List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);

        if(setmealIds != null && setmealIds.size()>0){
            //查到了套餐，说明当前菜品被套餐关联了，不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);

        }

        //删除菜品表中的菜品数据
       /* for(Long id : ids){
            dishMapper.deleteById(id);

            //3.删除菜品关联的口味数据
            dishFlavorMapper.deleteByDishId(id);
        }*/
        //上述代码每一次for循环，都需要发出两条sql语句。优化代码：只需要一条sql就可以实现把需要的菜品删除掉
        //根据菜品id集合批量删除菜品数据
        //sql: delete from dish where id in(?,?,?)
        dishMapper.deleteByIds(ids);

        //根据菜品id集合批量删除关联的口味数据
        //sql: delete from dish_flavor where dish_id in(?,?,?)
        dishFlavorMapper.deleteByDishIds(ids);
    }


    /**
     * 根据id查询菜品和对应的口味数据
     *
     * 在菜品管理列表页面点击修改按钮，跳转到修改菜品页面，在修改页面回显菜品相关信息并进行修改，最后点击保存按钮完成修改操作。
     *
     *   接口：
     * - 根据id查询菜品和对应的口味数据   因为是修改功能，请求方式可设置为PUT。
     * - 根据类型查询分类(已实现)
     * - 文件上传(已实现)
     * - 修改菜品
     * @param id
     * @return
     */
    @Transactional  //事务
    public DishVO getByIdWithFlavor(Long id){
        //1.根据id查询菜品数据
        Dish dish = dishMapper.getById(id);

        //2.根据菜品id查询对应的口味数据
        List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);

        //3.将查询到的数据封装到DishVO
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish,dishVO);
        dishVO.setFlavors(dishFlavors);

        return dishVO;
    }
    /**
     * 根据id修改菜品基本信息和对应的口味信息
     * 先统一删除关联的全部口味信息，再插入新的口味信息（不用关心原来是否有关联的口味信息）
     * @param dishDTO
     */
    @Override
    @Transactional  //事务
    public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);

        //修改菜品表基本信息(dishDto里面包含了口味数据，而当前修改只是修改菜品表的基本信息，所以传一个dish对象更好)
        dishMapper.update(dish);

        //删除原有的口味数据
        dishFlavorMapper.deleteByDishId(dishDTO.getId());

        //重新插入口味数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size()>0){
            flavors.forEach(dishFlavor -> {               //没看懂
                dishFlavor.setDishId(dishDTO.getId());
            });

            //向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }
    }

    @Autowired
    private SetmealMapper setmealMapper;
    /**
     * 菜品起售停售
     *
     * 菜品起售表示该菜品可以对外售卖，在用户端可以点餐，菜品停售表示此菜品下架，用户端无法点餐。

     * 业务规则为：如果执行停售操作，则包含此菜品的套餐也需要停售。
     * @param status
     * @param id
     */
    @Override
    @Transactional  //事务
    public void startOrStop(Integer status, Long id) {

        Dish dish = Dish.builder()
                .id(id)
                .status(status)
                .build();
        dishMapper.update(dish);

        if (status ==StatusConstant.DISABLE){
            //如果是停售操作，还需要将包含当前菜品的套餐也停售
            List<Long> dishIds = new ArrayList<>();
            dishIds.add(id);

            //select setmeal_id from setmeal_dish where dish_id in (?,?,?)
            List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds);   //根据菜品id查询对应的套餐id
            if (setmealIds != null && setmealIds.size()>0){   //为什么调用size方法后面要加一个（）
                for(Long setmealId : setmealIds){
                    Setmeal setmeal = Setmeal.builder()
                            .id(setmealId)
                            .status(StatusConstant.DISABLE)
                            .build();
                    setmealMapper.update(setmeal);
                }
            }
        }
    }


    /**
     * 根据分类id查询菜品
     *
     * 新增套餐：
     * 业务规则：
     * - 套餐名称唯一
     * - 套餐必须属于某个分类
     * - 套餐必须包含菜品
     * - 名称、分类、价格、图片为必填项
     * - 添加菜品窗口需要根据分类类型来展示菜品
     * - 新增的套餐默认为停售状态
     *
     * 接口设计（共涉及到4个接口）：
     * - 根据类型查询分类（已完成）
     * - 根据分类id查询菜品
     * - 图片上传（已完成）
     * - 新增套餐，在SetmealController中
     *
     * @param categoryId
     * @return
     */
    @Override
    public List<Dish> list(Long categoryId) {   //bulider和build注释和build方法是嘛意思-->因为有builder注解
        Dish dish = Dish.builder()
                .categoryId(categoryId)
                .status(StatusConstant.ENABLE)
                .build();
        return dishMapper.list(dish);
    }
    /**
     * 条件查询菜品和关联的口味
     * @param dish
     * @return
     */
    @Override
    public List<DishVO> listWithFlavor(Dish dish) {
        //动态条件查询菜品数据。查出来一个集合
        List<Dish> dishList = dishMapper.list(dish);

        //把查出来的所有数据都封装成一个个dishVo，所以创建一个dishVo的集合
        List<DishVO> dishVOList = new ArrayList<>();

        for (Dish d : dishList){
            DishVO dishVO = new DishVO();
            BeanUtils.copyProperties(d,dishVO);   //dishList复制到dishVo

            //根据菜品id查询对应的口味
            List<DishFlavor> flavors = dishFlavorMapper.getByDishId(d.getId());

            dishVO.setFlavors(flavors);
            //把这个vo丢到list集合中
            dishVOList.add(dishVO);
        }
        return dishVOList;
    }
}
